package com.lemon.mask.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.lemon.mask.annotation.DataMask;
import com.lemon.mask.strategy.DataMaskStrategy;
import com.lemon.mask.strategy.DataMaskStrategyContext;
import com.lemon.mask.strategy.DesensitizedConfig;
import com.lemon.mask.util.DesensitizedUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.Objects;

/**
 * @author lemon
 * @version 1.0
 * @description: 服务返回数据脱敏
 * @date Create by lemon on 2020-07-03 21:08
 */
@Slf4j
public class JacksonDataMaskSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private DesensitizedConfig desensitizedConfig;

    public JacksonDataMaskSerializer() {

    }

    public JacksonDataMaskSerializer(DesensitizedConfig desensitizedConfig) {
        this.desensitizedConfig = desensitizedConfig;
    }

    /**
     * Method that can be called to ask implementation to serialize
     * values of type this serializer handles.
     *
     * @param value         Value to serialize; can <b>not</b> be null.
     * @param jsonGenerator Generator used to output resulting Json content
     * @param serializers   Provider that can be used to get serializers for
     */
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
        if (StringUtils.isBlank(value)) {
            jsonGenerator.writeObject(value);
            return;
        }

        if (desensitizedConfig == null) {
            log.debug("Not mask");
            jsonGenerator.writeObject(value);
            return;
        }

        DataMaskStrategy maskStrategy = DataMaskStrategyContext.getDataMaskStrategy(desensitizedConfig.getSensitiveType());

        if (maskStrategy == null) {
            log.warn("No matching Mask Strategy found for type [{}]", desensitizedConfig.getSensitiveType());
            jsonGenerator.writeObject(value);
            return;
        }

        String newValue = maskStrategy.mask(desensitizedConfig, value);

        log.debug("Find MaskStrategy [{}]", maskStrategy.getClass());
        jsonGenerator.writeString(newValue);
    }

    /**
     * Method called to see if a different (or differently configured) serializer
     * is needed to serialize values of specified property.
     * Note that instance that this method is called on is typically shared one and
     * as a result method should <b>NOT</b> modify this instance but rather construct
     * and return a new instance. This instance should only be returned as-is, in case
     * it is already suitable for use.
     *
     * @param prov     Serializer provider to use for accessing config, other serializers
     * @param property Method or field that represents the property
     *                 (and is used to access value to serialize).
     *                 Should be available; but there may be cases where caller cannot provide it and
     *                 null is passed instead (in which case impls usually pass 'this' serializer as is)
     * @return Serializer to use for serializing values of specified property;
     * may be this instance or a new instance.
     * @throws JsonMappingException
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (property == null) {
            return prov.findNullValueSerializer(null);
        }

        if (Objects.equals(property.getType().getRawClass(), String.class)) {
            DataMask dataMask = property.getAnnotation(DataMask.class);

            if (dataMask == null) {
                dataMask = property.getContextAnnotation(DataMask.class);
            }

            if (dataMask != null) {
                DesensitizedConfig desensitizedConfig = DesensitizedUtils.getDesensitizedConfig(dataMask);
                return new JacksonDataMaskSerializer(desensitizedConfig);
            }
        }

        return prov.findValueSerializer(property.getType(), property);
    }
}
